package cn.sh.tools.service.component.wechat;

import cn.hutool.cache.Cache;
import cn.hutool.cache.impl.FIFOCache;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.sh.tools.annotation.WeChat;
import cn.sh.tools.domain.constant.CommonConstant;
import cn.sh.tools.domain.constant.WeChatConstant;
import cn.sh.tools.domain.constant.WeChatErrEnum;
import cn.sh.tools.domain.dto.WcMsgPutDto;
import cn.sh.tools.domain.vo.WcDepartmentVo;
import cn.sh.tools.domain.vo.WcMsgPutVo;
import cn.sh.tools.domain.vo.WcTokenVo;
import cn.sh.tools.util.file.FileUtils;
import cn.sh.tools.util.file.SecureUtils;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;

/**
 * <pre>
 *  1. 获取接口凭证
 *      参考：https://developer.work.weixin.qq.com/document/path/91039
 *  2. 获取登录用户信息
 *  3. 调用企业接口
 *  3.1 向成员发送消息
 * </pre>
 */
@Service
@Slf4j
public class BusinessWeChatService {

    /**
     * 缓存 access_token(接口凭证)，用于后续接口的调用。
     */
    private Cache<String, String> WE_CHAT_COMMON_CACHE_MAP;

    /**
     * 标记哪些属性加密了
     */
    private static final Set<String> ENCRYPT_FIELD_SET = Sets.newHashSet(WeChatConstant.CORP_ID,
            WeChatConstant.AGENT_ID, WeChatConstant.CORP_SECRET);

    @PostConstruct
    public void init() {
        this.storageAccessToken();
    }

    /**
     * 1. 存储接口凭证
     * 请求地址：https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
     * 开发者需要缓存access_token，用于后续接口的调用（注意：不能频繁调用gettoken接口，否则会受到频率拦截）。当access_token失效或过期时，需要重新获取。
     */
    public void storageAccessToken() {
        List<String> contentList = FileUtils.readFileFromClasspath(CommonConstant.BUSINESS_WE_CHAT, StandardCharsets.UTF_8);
        if (CollUtil.isEmpty(contentList)) {
            log.info("企业微信配置文件为空");
            return;
        }

        if (null != WE_CHAT_COMMON_CACHE_MAP) {
            WE_CHAT_COMMON_CACHE_MAP.clear();
        }
        WE_CHAT_COMMON_CACHE_MAP = new FIFOCache<>(contentList.size());

        for (String line : contentList) {
            // 跳过注释
            if (line.startsWith(WeChatConstant.NOTE_KEY_WORD)) {
                continue;
            }
            String key = line.substring(0, line.indexOf(CommonConstant.EQUAL_SIGN));
            String value = line.substring(line.indexOf(CommonConstant.EQUAL_SIGN) + 1);
            if (ENCRYPT_FIELD_SET.contains(key)) {
                WE_CHAT_COMMON_CACHE_MAP.put(key, SecureUtils.getDecryptPwdByDes(value, CommonConstant.SECURE_KEY));
            }
        }

        String accessTokenJson = this.getAccessTokenOnline();
        if (StrUtil.isBlank(accessTokenJson)) {
            log.info("企业微信获取接口凭证失败");
            return;
        }
        WcTokenVo wcTokenVo = JSON.parseObject(accessTokenJson, WcTokenVo.class);
        this.judgeResFail(wcTokenVo.getErrcode(), StrUtil.format("获取接口凭证失败,错误信息【{}】", accessTokenJson), CommonConstant.NO);
        WE_CHAT_COMMON_CACHE_MAP.put(WeChatConstant.Token.ACCESS_TOKEN_CACHE_KEY, wcTokenVo.getAccess_token());
        log.info("企业微信接口凭证存储完成");
    }

    /**
     * 获取接口凭证
     */
    private String getAccessTokenOnline() {
        String URIString = WeChatConstant.Token.BASE.concat(WeChatConstant.QUESTION_MARK)
                .concat(WeChatConstant.CORP_ID).concat(WeChatConstant.FIELDREF_DELIM)
                .concat(WE_CHAT_COMMON_CACHE_MAP.get(WeChatConstant.CORP_ID))
                .concat(WeChatConstant.EQUAL_SIGN)
                .concat(WeChatConstant.CORP_SECRET).concat(WeChatConstant.FIELDREF_DELIM)
                .concat(WE_CHAT_COMMON_CACHE_MAP.get(WeChatConstant.CORP_SECRET));
        // 获取企业微信的接口凭证
        return HttpUtil.get(URIString, CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * 从 WE_CHAT_COMMON_CACHE_MAP 中获取指定的 key
     * <p>
     * 示例：this.judgeWeChatCommonCacheAvailable(WeChatConstant.Token.ACCESS_TOKEN_CACHE_KEY);
     */
    public String judgeWeChatCommonCacheAvailable(String key) {
        /*
         * 什么情况下可以从 WE_CHAT_COMMON_CACHE_MAP 中获取指定 key？
         * ① WE_CHAT_COMMON_CACHE_MAP 不为空，且 enable 为 true。
         * ③ WE_CHAT_COMMON_CACHE_MAP 中包含 key。
         */
        boolean searchFlag = null != WE_CHAT_COMMON_CACHE_MAP && !WE_CHAT_COMMON_CACHE_MAP.isEmpty()
                && WE_CHAT_COMMON_CACHE_MAP.containsKey(CommonConstant.ENABLE)
                && "true".equals(WE_CHAT_COMMON_CACHE_MAP.get(CommonConstant.ENABLE))
                && WE_CHAT_COMMON_CACHE_MAP.containsKey(key);

        if (!searchFlag) {
            this.storageAccessToken();
        }

        return WE_CHAT_COMMON_CACHE_MAP.get(key);
    }

    /**
     * 获取部门成员信息
     * 请求地址：https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID
     *
     * @param accessToken  接口凭证
     * @param departmentId 部门id
     * @param fetchChild   1-递归获取（获取本部门以及子部门人员），0-只获取本部门
     * @param cycle        最大重试次数
     */
    public WcDepartmentVo getWcDepartmentStaffInfo(String accessToken, String departmentId, String fetchChild, Integer cycle) {
        // 重试次数已经使用完成，直接返回空
        if (cycle < 0) {
            return null;
        }
        String URIString = this.getWcDepartmentStaffURI(accessToken, departmentId, fetchChild);
        String departmentStaffResJson = HttpUtil.get(URIString, CharsetUtil.CHARSET_UTF_8);
        if (StrUtil.isBlank(departmentStaffResJson)) {
            log.info("获取部门成员信息失败,{}", URIString);
            return null;
        }

        WcDepartmentVo wcDepartmentVo = JSON.parseObject(departmentStaffResJson, WcDepartmentVo.class);
        String errcode = wcDepartmentVo.getErrcode();
        this.judgeResFail(errcode, StrUtil.format("获取部门成员信息失败【{}】", departmentStaffResJson), CommonConstant.YES);
        if (StrUtil.isNotBlank(errcode) && WeChatErrEnum.ACCESS_TOKEN_INVALID.getErrCode().equals(errcode)) {
            this.storageAccessToken();
            this.getWcDepartmentStaffInfo(this.judgeWeChatCommonCacheAvailable(WeChatConstant.Token.ACCESS_TOKEN_CACHE_KEY), departmentId, fetchChild, cycle - 1);
        }
        return wcDepartmentVo;
    }

    /**
     * 获取部门成员的URI
     *
     * @param accessToken 接口凭证
     */
    @WeChat
    public String getWcDepartmentStaffURI(String accessToken, String departmentId, String fetchChild) {
        return WeChatConstant.DepartmentStaff.BASE.concat(WeChatConstant.QUESTION_MARK)
                .concat(WeChatConstant.DepartmentStaff.ACCESS_TOKEN).concat(WeChatConstant.FIELDREF_DELIM).concat(accessToken)
                .concat(WeChatConstant.EQUAL_SIGN)
                .concat(WeChatConstant.DepartmentStaff.DEPARTMENT_ID).concat(WeChatConstant.FIELDREF_DELIM).concat(departmentId)
                .concat(WeChatConstant.EQUAL_SIGN)
                .concat(WeChatConstant.DepartmentStaff.FETCH_CHILD).concat(WeChatConstant.FIELDREF_DELIM).concat(fetchChild);
    }

    /**
     * 消息推送
     *
     * @param accessToken 接口凭证
     * @param cycle       最大重试次数
     */
    public void msgPut(String accessToken, WcMsgPutDto wcMsgPutDto, Integer cycle) {
        String msgPutReqJson = JSON.toJSONString(wcMsgPutDto);
        String URIString = this.getWcMsgPutURI(accessToken);
        String msgPutResJson = HttpUtil.post(URIString, msgPutReqJson);
        if (StrUtil.isBlank(msgPutResJson)) {
            log.info("消息推送失败,{}", msgPutReqJson);
        }

        WcMsgPutVo wcMsgPutVo = JSON.parseObject(msgPutResJson, WcMsgPutVo.class);
        String errcode = wcMsgPutVo.getErrcode();
        this.judgeResFail(errcode, StrUtil.format("消息推送失败【{}】", msgPutResJson), CommonConstant.YES);
        if (StrUtil.isNotBlank(errcode) && WeChatErrEnum.ACCESS_TOKEN_INVALID.getErrCode().equals(errcode)) {
            this.storageAccessToken();
            this.msgPut(this.judgeWeChatCommonCacheAvailable(WeChatConstant.Token.ACCESS_TOKEN_CACHE_KEY), wcMsgPutDto, cycle - 1);
        }
    }

    @WeChat
    public String getWcMsgPutURI(String accessToken) {
        return WeChatConstant.MsgPut.BASE.concat(WeChatConstant.QUESTION_MARK)
                .concat(WeChatConstant.MsgPut.ACCESS_TOKEN).concat(WeChatConstant.FIELDREF_DELIM).concat(accessToken);
    }

    /**
     * 开发者需根据errcode存在且不为0判断为失败，否则为成功。
     *
     * @param throwOrLogFlag 抛出异常或打印异常信息，0-抛出异常，1-打印异常。
     */
    private void judgeResFail(String errCode, String errMsg, String throwOrLogFlag) {
        // errCode 存在且不为0，则说明接口调用失败。
        boolean failFlag = StrUtil.isNotBlank(errCode) && !CommonConstant.NO.equalsIgnoreCase(errCode);

        if (failFlag) {
            if (CommonConstant.NO.equals(throwOrLogFlag)) {
                throw new RuntimeException(errMsg);
            } else if (CommonConstant.YES.equals(throwOrLogFlag)) {
                log.info(errMsg);
            }
        }
    }

}
